import { Calculator, Calculator_arg } from "./Calculator";
import { Update_mode } from "./Enums";
import { EventDispatcher } from "./EventDispatcher";

interface Connection {
    srcProp: string,
    dstVo: ValueObject,
    dstProp: string;
}

/**
 * srcVoKey 数据源头vo在目标vo中的属性名
 * srcProp 源属性
 * dstProp 需要随源属性变化的目标属性
 */
interface Binding {
    srcProp: string,
    srcVoKey: string,
    dstProp: string;
}

/**
 * 数据对象类
 */
export class ValueObject {
    [x: string]: any;
    // 属性
    private _props: Map<string, any> = new Map();
    private _settedProp: Set<string> = new Set();
    // 脏标记，当一个属性被视为脏，则它需要更新，具体更新时机根据Bind_mode决定
    private _dirtys: Set<string> = new Set();
    // 更新堆栈，用于存放记录需要更新的闲时更新属性
    private _updateList: Array<string> = new Array();
    private _nextTickUpdateList: Array<string> = new Array();

    // 属性计算器，用于更新属性时计算
    protected _calculators: Map<string, Calculator> = new Map();
    // 属性的更新模式
    private _updateModes: Map<string, Update_mode> = new Map();
    // 关系记录
    private _connections: Array<Connection> = new Array();
    // private _pre_connections: Array<PreConnection> = new Array();
    protected _bindings: Array<Binding> = new Array();

    private _eventDisp: EventDispatcher = new EventDispatcher();

    constructor(props?: Array<string> | null) {
        if (props) {
            for (let i = 0; i < props.length; i++) {
                let s = props[i].replace(/\s/g, "").split("=");
                let value = null;
                if (s[1]) {
                    value = isNaN(Number(s[1])) ? s[1] : Number(s[1]);
                }
                this.setProp(s[0], value);
            }
        }
    }

    public setProp(prop: string, value?: any) {
        if (!this._settedProp.has(prop)) {
            Object.defineProperty(this, prop, {
                get: function () {
                    return this.get(prop);
                },
                set: function (value: any) {
                    this.set(prop, value);
                }
            });
            this._settedProp.add(prop);
        }

        if (value || value == 0) {
            this[prop] = value;
        }
    }

    // 设置vo的属性值
    public set(key: string, value: any) {
        let old = this._props.get(key);
        if (typeof old == "object") {
            // 解除关系
            old.removeConnectionsByTarget(this);
        }
        this._props.set(key, value);
        if (value) {
            // 建立新的关系
            this.addConnectionsWithVo(key, value);
        }
        this.connectionImpluse(key);
        this.emit(key, value);
    }

    private connectionImpluse(key: string) {
        for (let i = 0; i < this._connections.length; i++) {
            let c = this._connections[i];
            if (c.srcProp == key) {
                c.dstVo.setDirty(c.dstProp);
            }
        }
    }

    // 读取vo的属性值
    public get(key: string) {
        if (this._dirtys.has(key)) {
            this._updateDirtyProp(key);
        }
        return this._props.get(key);
    }

    public setDirty(key: string) {
        if (this._dirtys.has(key))
            return;
        this._dirtys.add(key);

        let mode = this._updateModes.get(key) || Update_mode.Immediately;
        switch (mode) {
            case Update_mode.Immediately:
                this._updateDirtyProp(key);
                break;
            case Update_mode.NextTick:
                this._nextTickUpdateList.push(key);
                break;
            case Update_mode.OnFree:
                this._updateList.push(key);
                break;
            case Update_mode.OnGet:
                break;
        }
        this.connectionImpluse(key);
    }

    /**
     * @param prop 属性名
     * @param fml 属性更新公式
     * @mode 数据更新模式，默认为立即更新
     * 例如:atk + hpMax * 0.1 + child.hpMax * 0.05
     */
    public bind(prop: string, fml: string, mode?: Update_mode) {
        // 删除所有空格
        fml = fml.replace(/\s/g, "");
        // 根据操作符分隔所有参数字符串
        let keys = fml.split(/[+=/*//\[\]]/g);
        // 提取所有操作符
        let opts = fml.match(/[+=/*//\[\]]/g);
        // 去除末尾的空字符串
        if (keys[keys.length] == "") {
            keys = keys.splice(keys.length - 1, 1);
        }

        if (!keys) {
            return;
        }

        this.setProp(prop);
        if (mode) {
            this.setUpdateMode(prop, mode);
        }

        let args = [];
        let map = new Map();
        fml = "";
        for (let i = 0; i < keys.length; i++) {
            let key = keys[i];
            let num = map.get(key);
            if (num || num == 0) {
                fml = fml + 'arguments[' + num + ']';
            } else if (isNaN(Number(key))) {
                map.set(key, args.length);
                num = args.length;
                let vo = "self";
                let prop = key;
                let spl = key.split('.');
                if (spl[1]) {
                    vo = spl[0];
                    prop = spl[1];
                }
                args.push({
                    vo: vo,
                    prop: prop,
                    default: null
                });
                fml = fml + 'arguments[' + num + ']';
            } else {
                fml = fml + key;
            }
            if (opts && opts[i])
                fml = fml + opts[i];

        }
        let calculator = {
            fml: fml,
            func: new Function("return " + fml),
            args: args
        };
        this.setCalculator(prop, calculator);
    }

    // 设置属性计算器
    public setCalculator(key: string, calculator: Calculator) {
        this._calculators.set(key, calculator);
        for (let i = 0; i < calculator.args.length; i++) {
            let arg = calculator.args[i];
            this._bindings.push({
                dstProp: key,
                srcVoKey: arg.vo,
                srcProp: arg.prop
            });
            let vo = arg.vo == "self" ? this : this.get(arg.vo);
            if (vo) {
                vo.addConnection(arg.prop, this, key);
            }
        }
        this.setDirty(key);
    }

    public setUpdateMode(key: string, mode: Update_mode) {
        this._updateModes.set(key, mode);
    }

    public addConnection(srcProp: string, dstVo: ValueObject, dstProp: string) {
        let connection = {
            srcProp: srcProp,
            dstVo: dstVo,
            dstProp: dstProp
        };
        this._connections.push(connection);
    }

    // 根据关系对象删除关系
    public removeConnectionsByTarget(target: ValueObject) {
        for (let i = this._connections.length - 1; i >= 0; i--) {
            let c = this._connections[i];
            if (c.dstVo == target) {
                this._connections.splice(i, 1);
            }
        }
    }

    private addConnectionsWithVo(key: string, vo: ValueObject) {
        let bindings = this.getBindingsBySrc(key);
        for (let i = 0; i < bindings.length; i++) {
            vo.addConnection(bindings[i].srcProp, this, bindings[i].dstProp);
            this.setDirty(bindings[i].dstProp);
        }
    }

    private getBindingsBySrc(key: string) {
        let ret = [];
        for (let i = 0; i < this._bindings.length; i++) {
            let b = this._bindings[i];
            if (b.srcVoKey == key) {
                ret.push(b);
            }
        }
        return ret;
    }

    // 闲时更新属性更新
    public updatePropOnFree() {
        for (let i = 0; i < this._updateList.length; i++) {
            let key = this._updateList[i];
            if (this._dirtys.has(key)) {
                this._updateDirtyProp(key);
                this._updateList.splice(0, i + 1);
                return;
            }
        }
        this._updateList = [];
    }

    // 更新被标记为脏的某个属性
    private _updateDirtyProp(key: string) {
        this._dirtys.delete(key);
        let calculator = this._calculators.get(key);
        if (!calculator)
            return;
        let args: any[] = [];
        for (let i = 0; i < calculator.args.length; i++) {
            let arg = this._parseArg(calculator.args[i]);
            args.push(arg);
        }
        let val = calculator.func.apply(this, args);
        this._props.set(key, val);
        this.emit(key, val);
    }

    // 解析计算属性更新时具体的参数值
    protected _parseArg(arg: Calculator_arg): any {
        let vo = arg.vo == "self" ? this : this.get(arg.vo);
        if (vo == "undefined" || vo == null)
            return arg.default;
        let prop = vo[arg.prop];
        if (prop == "undefined" || prop == null)
            return arg.default;
        return prop;
    }

    public on(key: string, fn: Function, listener: any, args?: any) {
        this._eventDisp.on(key, fn, listener, args);
    }

    public emit(key: string, value: any) {
        this._eventDisp.emit(key, value);
    }

    public destroy() {
        for (let i = 0; i < this._bindings.length; i++) {
            let b = this._bindings[i];
            let vo = this.get(b.srcVoKey);
            if (vo) {
                vo.removeConnectionsByTarget(this);
            }
        }
        this._eventDisp.destroy();
    }

    private _updatePropForNextTip() {
        let ret = this._nextTickUpdateList.length > 0;
        for (let i = 0; i < this._nextTickUpdateList.length; i++) {
            this._updateDirtyProp(this._nextTickUpdateList[i]);
        }
        this._nextTickUpdateList = [];
        return ret;
    }

    public update(dt: number) {
        if (!this._updatePropForNextTip())
            this.updatePropOnFree();
    }
}